package com.smasher.mqtt;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;


import com.smasher.mqtt.config.EasyMqttOptions;
import com.smasher.mqtt.config.MessageDispatcher;
import com.smasher.mqtt.interfaces.MqttSubject;
import com.smasher.mqtt.log.SmasherLog;
import com.smasher.mqtt.message.LastWillMessage;
import com.smasher.mqtt.receivers.TopicMessageReceiver;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.nio.charset.StandardCharsets;


/**
 * EasyMqttClient
 *
 * @author HXZ_Android
 */
public class EasyMqttClient implements MqttSubject {

    /**
     * 静态内部类实现单例，线程安全且延迟加载
     */
    private static class EasyHolder {
        private  static final EasyMqttClient INSTANCE = new EasyMqttClient();
    }
    /**
     * 单例
     */
    public static EasyMqttClient getInstance() {
        return EasyHolder.INSTANCE;
    }

    /**
     * 默认的连接参数
     */
    private EasyMqttOptions defOptions;

    /**
     * 上下文
     */
    private Context context;

    /**
     * 获取上下文
     */
    public Context getContext() {
        return context;
    }

    /**
     * MQTT客户端
     */
    private MqttAndroidClient sClient;

    /**
     * MQTT连接参数
     */
    private MqttConnectOptions mConnectOptions;

    /**
     * 设备唯一标识
     */
    private DeviceUuidFactory factory;

    /**
     * 获取默认的配置参数
     */
    public EasyMqttOptions getDefOptions() {
        return defOptions == null ? EasyMqttOptions.getDefaultOptions() : defOptions;
    }

    public static final String TAG = "EasyMqttClient";

    private boolean addLog = true;

    private final MessageDispatcher mDispatcher = new MessageDispatcher();

    @Override
    public boolean isConnected() {
        return sClient != null && sClient.isConnected();
    }

    @Override
    public void createConnection(EasyMqttOptions options, Context context) {
        this.context = context;
        this.defOptions = options;

        this.factory = new DeviceUuidFactory(context);
        if (options.getAddress() == null) {
            throw new RuntimeException("请在初始化的时候设置地址");
        }
        // 设备唯一标识
        String defaultClientId = factory.getDeviceUuid().toString();
        // 设置客户端标识
        String clientId = options.getClientId() == null ? defaultClientId : options.getClientId();
        // 创建MQTT客户端
        sClient = new MqttAndroidClient(context, options.getAddress(), clientId);
        // 设置MQTT监听并且接受消息
        sClient.setCallback(mMqttCallback);
        // 注册资源
        // sClient.registerResources(context);

        mConnectOptions = new MqttConnectOptions();
        // 清除缓存
        mConnectOptions.setCleanSession(false);
        // 设置超时时间，单位：秒
        mConnectOptions.setConnectionTimeout(options.getConnectTimeout());
        // 心跳包发送间隔，单位：秒
        mConnectOptions.setKeepAliveInterval(options.getKeepAliveInterval());
        // 自动重连
        mConnectOptions.setAutomaticReconnect(options.isAutomaticReconnect());
        // 用户名
        mConnectOptions.setUserName(options.getUserName());
        // 密码-----将字符串转换为字符串数组
        mConnectOptions.setPassword(options.getPassWord().toCharArray());

        // 最后的遗嘱
        // MQTT本身就是为信号不稳定的网络设计的，所以难免一些客户端会无故的和Broker断开连接。
        // 当客户端连接到Broker时，可以指定LWT，Broker会定期检测客户端是否有异常。
        // 当客户端异常掉线时，Broker就往连接时指定的topic里推送当时指定的LWT消息。
        if (options.getDefaultTopic() != null) {
            String topic = options.getDefaultTopic();
            LastWillMessage willMessage = options.getDefaultTopicWillMessage();
            if (willMessage == null) {
                willMessage = new LastWillMessage(clientId);
            }
            int qos = options.getDefaultTopicQos();
            boolean retained = options.isDefaultTopicRetained();
            try {
                mConnectOptions.setWill(topic, willMessage.pack(), qos, retained);
            } catch (Exception e) {
                SmasherLog.injectActionLog("Exception Occurred:" + e.getMessage());
            }
        }

        doClientConnection(options);
    }

    /**
     * 连接MQTT服务器
     */
    private void doClientConnection(EasyMqttOptions options) {
        SmasherLog.injectLog("doClientConnection: ");
        if (!sClient.isConnected() && isConnectIsNormal()) {
            try {
                sClient.connect(mConnectOptions, null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        SmasherLog.injectActionLog("连接成功 ");
                        doActionAfterConnect(options);
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        SmasherLog.injectActionLog(exception.getMessage());
                    }
                });
            } catch (MqttException e) {
                SmasherLog.injectLog(e.getMessage());
            }
        }
    }


    private void doActionAfterConnect(EasyMqttOptions options) {
        String topic = options.getDefaultTopic();
        if (topic != null) {
            int qos = options.getDefaultTopicQos();
            subscribe(topic, qos);
        }
    }

    @Override
    public void subscribe(String topic, int qos) {
        try {
            if (sClient != null && sClient.isConnected()) {
                sClient.subscribe(topic, qos, null, new IMqttActionListener() {

                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        SmasherLog.injectActionLog("subscribe: success");
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        SmasherLog.injectActionLog("subscribe: failure");
                    }
                });

            }
        } catch (MqttException e) {
            SmasherLog.injectLog("MqttException in subscribe");
        }
    }

    @Override
    public void unsubscribe(String topic) {
        try {
            if (sClient != null && sClient.isConnected()) {
                sClient.unsubscribe(topic);
            }
        } catch (MqttException e) {
            SmasherLog.injectLog("MqttException in unsubscribe");
        }
    }

    @Override
    public void publish(String topic, String msg, int qos, boolean retained) {
        if (sClient != null && sClient.isConnected()) {
            try {
                sClient.publish(topic, msg.getBytes(StandardCharsets.UTF_8), qos, retained);
            } catch (MqttException e) {
                SmasherLog.injectLog("MqttException in publish");
            }
        }
    }

    @Override
    public void publish(String topic, MqttMessage mqttMessage) {
        if (sClient != null && sClient.isConnected()) {
            try {
                sClient.publish(topic, mqttMessage);
            } catch (MqttException e) {
                SmasherLog.injectLog("MqttException in publish");
            }
        }
    }


    @Override
    public void addMessageReceive(TopicMessageReceiver receiver) {
        mDispatcher.addReceiver(receiver);
    }

    @Override
    public void removeMessageReceive(TopicMessageReceiver receiver) {
        mDispatcher.removeReceiver(receiver);
    }

    @Override
    public void disconnect() {
        disconnect(false);
    }

    private void disconnect(boolean release) {
        try {
            if (sClient != null && sClient.isConnected()) {
                SmasherLog.injectLog("destroy: after unregister");
                sClient.disconnect(null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        if (release) {
//                            sClient.unregisterResources();
                            sClient.close();
                        }
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {

                    }
                });
            }
        } catch (MqttException e) {
            SmasherLog.injectLog(e.getMessage());
        }
    }


    @Override
    public void destroy() {
        SmasherLog.injectActionLog("destroy: ");
        disconnect(true);
        mDispatcher.release();
    }


    private final MqttCallback mMqttCallback = new MqttCallback() {
        @Override
        public void connectionLost(Throwable cause) {
            if (cause != null) {
                SmasherLog.injectActionLog("连接断开：" + cause.getMessage());
            } else {
                SmasherLog.injectActionLog("连接断开");
            }
        }

        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
            mDispatcher.dispatch(topic, message);
        }

        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
            SmasherLog.injectActionLog("deliveryComplete: ");
        }
    };

    private boolean isConnectIsNormal() {
        ConnectivityManager connectivityManager = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = connectivityManager.getActiveNetworkInfo();
        if (info != null && info.isAvailable()) {
            String name = info.getTypeName();
            SmasherLog.injectLog("MQTT当前网络名称：" + name);
            return true;
        } else {
            SmasherLog.injectLog("MQTT 没有可用网络");
            return false;
        }
    }

}
